<template>
  <div class="gcode-viewer">
    <canvas id="canvas"></canvas>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch } from 'vue-property-decorator'
import * as THREE from "three"
import Parser from "./parser.js";
import UrlReader from "./reader.js";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

@Component({})
export default class GcodeViewer extends Vue {

  @Prop({default: ""}) private url!: string;
  @Prop({default: 1}) private visible!: number;
  @Prop({default: {}}) private opts: any;
  @Prop() private onProgress: any;
  @Prop() private onFinished: any;
  @Prop() private onError: any;

  @Watch('visible', { immediate: true, deep: true })
  private onVisiblechanged(val: any){
    this.visibility = val;
    if (this.scene) this.updateMaterial()
  }

  scene: any | null = null;
  camera: any | null = null;
  renderer: any | null = null;
  controls: any | null = null;

  reader: any | null = null;
  parser: any | null = null;

  options = {
    clearColor: "#000",      // 背景颜色

    layerColor: "#ccc",      // 材质颜色

    topLayerColor: "#ccc",   // 顶层材质颜色

    speed: 100,              // 加载速度

    linewidth: 1,            // 材质宽度

    quality: 1,              // 模型质量

    animation: true,         // 加载动画

    autoLoad: true,          // 自动加载

    gridHelper: true,        // 辅助线

    gridHelperColor: "#abc", // 辅助线颜色

    axesHelper: false,       // 坐标系辅助线

    lightHelper: false,      // 平行光辅助
  };

  progress: any = {};        // 模型加载信息

  loadFinished = false;      // 加载结束状态

  timer: any | null = null;  // 加载定时器

  visibility = 1;            // 模型可见度

  directionalLight:any|null = null;

  mounted(): void {
    this.options = Object.assign(this.options, this.$props.opts);
    this.visibility = this.$props.visible < 0 ? 0 : Math.min(this.$props.visible, 1);
    
    this.parser = new Parser({
      quality: 1,
      onProgress: (progress:any, points:object[]) => {
        this.progress = progress;
        this.renderGcode(points)
        if (this.$props.onProgress) this.$props.onProgress(this.progress)
      },
      onFinished: (progress:any) => {
        this.progress = progress;
        if (this.$props.onFinished) this.$props.onFinished(this.progress)
      }
    });
    this.reader = new UrlReader({ url: this.$props.url, chunkSize: 1024*64});
    this.initCanvas(document.getElementById('canvas'))

    if (this.options.autoLoad) {
      setTimeout(() => this.parser.parseGcode(this.reader), 500);
    }
  }
  //注意： 这里释放场景，并未处理必要的scene子物体的 geometry 、material 等
  beforeDestroy() {
    try {
      this.clearObjects();
      this.scene.clear();
      this.renderer.dispose();
      this.renderer.forceContextLoss();
      this.renderer.content = null;
      let gl = this.renderer.domElement.getContext("webgl");
      gl && gl.getExtension("WEBGL_lose_context").loseContext();
    }catch (e) {
      console.log(e)
    }
  }

  clearCatche(){
    clearTimeout(this.timer);
    this.timer = null;
  }

  gettime(){
    let dd = new Date();
    return dd.getTime();
  }

  renderGcode(points:any) {
    const helper = this.scene.getObjectByName("helper");
    if (!helper && this.progress) {
      this.updateCenter()
      // 添加网格坐标辅助线
      this.initGridHelper(10);
    }
    this.scene.traverse((child:any) => {
      if (child.type == "LineSegments") {
        child.material.color = new THREE.Color(this.options.layerColor);
        child.material.needsUpdate = true;
      }
    });
    this.gcodeLines(this.scene, points, true);
    this.updateMaterial()
  }

  updateMaterial(): void {
    const layer_limit = this.progress ? Math.ceil(this.visibility * this.progress.LAYER_COUNT) : {};
    this.scene.traverse((child:any) => {
      if (child.type == "LineSegments") {
        /**
         * 判断是否为最后一层
         * 条件1：已添加的最后一层 = 当前打印进度  == 最后一层打印层数的进度
         * 条件2：可显示的最后一层 = 打印进度 < 可显示的进度
         * **/
        const isTopLayer = Number(child.name) == layer_limit || Number(child.name) == this.progress.layer ? true : false;
        child.material.color = new THREE.Color(isTopLayer ? this.options.topLayerColor : this.options.layerColor);
        child.material.visible = layer_limit && Number(child.name) <= layer_limit ? true : false;
        child.material.needsUpdate = true;
      }
    });
  }

  /**
   * ---------canvas对象截图--------------
   * 可通过设置preserveDrawingBuffer为true来实现，但会导致性能下降，
   * preserveDrawingBuffer为true的时候，需要从A缓冲区复制到B缓冲，
   * preserveDrawingBuffer为false的时候，交换双缓冲，比较快。
   * 所以获取截图的时候调用renderer.render()，
   * 不让canvas清空就可以不用设置preserveDrawingBuffer为true了。
   */
  canvas2image(){
    this.renderer.render(this.scene, this.camera);
    var canvas = this.renderer.domElement;//获取canvas对象
    return canvas.toDataURL();
  }

  updateCenter(): void {
    const { MINX, MINY, MINZ, MAXX, MAXY, MAXZ } = this.progress;
    const basrCenter_x = (Number(MINX) + Number(MAXX)) / 2;
    const basrCenter_y = (Number(MINY) + Number(MAXY)) / 2;
    const basrCenter_z = (Number(MINZ) + Number(MAXZ)) / 2;
    const lenght_x = MAXX - MINX;
    this.camera.position.set(basrCenter_x, basrCenter_y-4*lenght_x, basrCenter_z)
    this.camera.lookAt(basrCenter_x, basrCenter_y, basrCenter_z)
    this.controls.target.set(basrCenter_x, basrCenter_y, basrCenter_z);
  }

  gcodeLines(parent:any, points: object[], isVisible = true): void {
    let vertices:any = [];
    for (let i = 0; i < points.length; i++) {
      const point:any = points[i];
      if (point.type == "extrude") {
        const {x,y,z} = point;
        const prev:any = points[i-1];
        vertices.push(prev.x,prev.y,prev.z,x,y,z)
      }
    }
    
    //Object generation
    const extrusionBuffer = new THREE.BufferGeometry();
    const extrusionMaterial:any = new THREE.LineDashedMaterial({
      opacity: 1,
      transparent: true,
      visible: isVisible,
      side: THREE.DoubleSide,
      color: this.options.topLayerColor,
      linewidth: this.options.linewidth || 2.0,
    });
    extrusionMaterial.castShadow = true;
    extrusionBuffer.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    const extrusionObject = new THREE.LineSegments(extrusionBuffer, extrusionMaterial);
    extrusionObject.name = `${this.progress.layer}`;
    //Cleanup
    vertices = [];
    extrusionBuffer.dispose();
    extrusionMaterial.dispose();

    parent.add(extrusionObject);
  }

  initCanvas(canvasDom: any): void {
    /**
     * 创建场景对象Scene
     */
    this.scene = new THREE.Scene();
    /**
     * 光源设置
     */
    this.initLights(this.scene)
    /**
     * 相机设置
     */
    this.camera = this.initCamera(this.scene)

    /**
     * 渲染器设置
     */
    this.renderer = this.initRender(canvasDom)

    // 执行渲染操作
    const render = () => {
      this.renderer.render(this.scene, this.camera);
      requestAnimationFrame(render);  //请求再次执行渲染函数render
    }
    /**
     * 控制器设置
     * Tip::执行THREE.OrbitControls构造函数时候，默认设置.target属性的值是Vector3(0,0,0);
     * 所以如果在执行new THREE.OrbitControls之前设置了camera.lookAt(特定位置);
     * 相当于再次设置camera.lookAt(new THREE.Vector3(0,0,0));
     */
    this.controls = this.initControls(this.camera, this.renderer)

    render();
  }

  initRender(canvas: any) {
    let renderer = new THREE.WebGLRenderer({
      antialias: true,  //默认为false。是否开启反锯齿
      alpha: true,      //默认为false。是否可以设置背景色透明
      canvas: canvas,
      // preserveDrawingBuffer: true,  // 保存three.js canvas画布上的信息
    });
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    renderer.setSize(window.innerWidth, window.innerHeight);//设置渲染区域尺寸
    renderer.setClearColor(this.options.clearColor, 1); //设置背景颜色
    renderer.shadowMap.enabled = true // 显示阴影
    renderer.shadowMap.type = THREE.PCFSoftShadowMap
    return renderer;
  }

  initLights(scene: any): void {
    var lightGroup = new THREE.Group();
    lightGroup.name = "lights";

    //环境光
    var ambient = new THREE.AmbientLight(0xffffff, 0.8);
    lightGroup.add(ambient);

    //点光源
    var pointLight = new THREE.PointLight(0xffffff, 1, 0, 1);
    pointLight.position.set(150, 150, 200); //点光源位置
    pointLight.castShadow = true;        //开启阴影
    lightGroup.add(pointLight); //点光源添加到场景中
    scene.add(lightGroup);
  }

  initCamera(scene: any) {
    let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000000);
    camera.position.set(-100, -100, 200); //设置相机位置
    camera.up = new THREE.Vector3(0, 0, 1);
    scene.add(camera);
    return camera;
  }

  initControls(camera: any, renderer: any) {
    let controls = new OrbitControls(camera, renderer.domElement);
    //设置控制器的中心点
    controls.target = new THREE.Vector3(100, 100, 100);
    // 如果使用animate方法时，将此函数删除
    // controls.addEventListener('change', render);
    // 使动画循环使用时阻尼或自转 意思是否有惯性
    controls.enableDamping = true;
    //动态阻尼系数 就是鼠标拖拽旋转灵敏度
    controls.dampingFactor = 0.25;
    //是否可以缩放
    controls.enableZoom = true;
    //是否自动旋转
    controls.autoRotate = false;
    controls.autoRotateSpeed = 1.5;
    //设置相机距离原点的最远距离
    controls.minDistance = 0.01;
    //设置相机距离原点的最远距离
    controls.maxDistance = 2000;
    //是否开启右键拖拽
    controls.enablePan = true;
    return controls;
  }

  initGridHelper(num = 10): void {
    if (!this.scene)  return;
    const { MINX, MINY, MAXX, MAXY } = this.progress;
    const basrCenter_x = (Number(MINX) + Number(MAXX)) / 2;
    const basrCenter_y = (Number(MINY) + Number(MAXY)) / 2;
    const gridWidth = Math.max(MAXX, MAXY);

    let gridHelperGroup = new THREE.Group();

    // 网格辅助线
    let gcp = new THREE.GridHelper(gridWidth, num, this.options.gridHelperColor, this.options.gridHelperColor);
    gridHelperGroup.name = 'helper';
    gcp.rotateX(Math.PI * 0.5); 
    gcp.translateX(basrCenter_x)
    gcp.translateZ(-basrCenter_y)

    // 红黄蓝坐标系
    if (this.options.axesHelper) {
      const axes = new THREE.AxesHelper(100);
      gridHelperGroup.add(axes)
    }

    gridHelperGroup.add(gcp);
    this.scene.add(gridHelperGroup)
  }

  clearObjects(){
    this.scene.traverse((child:any) => {
      child.geometry.dispose();
      child.material.dispose();
      this.scene.remove(child);
    });
  }

  shadowFloor(parent:any):void{
    // 平面几何
    const groundGeometry = new THREE.PlaneGeometry(100, 100)
    const groundMaterial = new THREE.MeshPhongMaterial({ color: 0xcc8866, side: THREE.DoubleSide })
    const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial)
    groundMesh.receiveShadow = true // 接受阴影
    parent.add(groundMesh)

    // 几何体
    const cubeSize = 10
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
    const cubeMat = new THREE.MeshPhongMaterial({ color: '#8AC' })
    const mesh = new THREE.Mesh(cubeGeo, cubeMat)
    mesh.castShadow = true // 投射阴影
    mesh.receiveShadow = true // 接受阴影
    mesh.position.set(0, 0, cubeSize/2)
    parent.add(mesh)

    //平行光源
    var directionalLight = new THREE.DirectionalLight(0xff0000);
    directionalLight.position.set(100, 100, 100); //点光源位置
    directionalLight.target.position.set(100, 100, 0)
    // directionalLight.target = mesh;
    directionalLight.castShadow = true

    // 辅助线
    if (this.options.lightHelper) {
      var lightHelper = new THREE.DirectionalLightHelper(directionalLight, 10);
      this.scene.add(lightHelper);
      this.scene.add(directionalLight); //点光源添加到场景中
    }
  }
}
</script>

<style scoped>
</style>
